/** @file memblock.c
* Implements all memblock functions declared in memblock.h .
* Allocates a block of memory and returns chunks of that block. When the block
* is depleted, a new block is allocated and linked together with the previous
* block of memory. Strings are kept in a special block which is expanded
* dynamically. The information about these chains of MemBlocks are kept in a
* MemBlockInfo data structure. Using MemBlock dramatically reduces the number 
* of calls to malloc() and free().
*
* @b Example:
* @code
*     struct MemBlockInfo* mem=NULL;    //memory blocks descriptor
*     struct Node* node1=NULL;          //sample element to allocate
*     struct Node* node2=NULL;          //another element to allocate
*
*     //Start a new chain of MemBlocks with default # of elements per block
*     mem=memInit(sizeof(struct Node));
*
*     node1=memGet(mem);                //get a new struct Node*
*     node2=memGet(mem);                //get another struct Node*
*
*     memFree(mem);                     //free all memory
* @endcode
*
* @author Victor Petrov <victor_petrov@harvard.edu>
* @date   Apr 02, 2011
* @cond
*------------------------------------------------------------------------------
* LEGAL:
*------------------------------------------------------------------------------
*
* Copyright (c) 2011, The President and Fellows of Harvard College
* Copyright (c) 2011, The Neuroinformatics Research Group at Harvard University
* All rights reserved.
*
* License: New BSD License   <http://creativecommons.org/licenses/BSD/>
*-------------------------
* Redistribution and use in source and binary forms, with or without 
* modification, are permitted provided that the following conditions are met:
*
*	Redistributions of source code must retain the above copyright notice, 
*	this list of conditions and the following disclaimer.
*
*	Redistributions in binary form must reproduce the above copyright notice, 
*	this list of conditions and the following disclaimer in the documentation
*	and/or other materials provided with the distribution.
*
*	Neither the name of the Neuroinformatics Research Group at Harvard, nor 
*	the names of its contributors may be used to endorse or promote products 
*	derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
* POSSIBILITY OF SUCH DAMAGE.
*------------------------------------------------------------------------------
* vim: ts=4
*------------------------------------------------------------------------------
* @endcond
*/

#ifdef PHPEXT
    #include "php.h"
    #include "php_vxp.h"
#endif

#include <assert.h>     /* assert() */
#include <string.h>     /* strlen(), memcpy(), memset() */

#include "memblock.h"   /* struct MemBlockInfo, struct MemBlock */

#ifndef PHPEXT
    /** Zend macros */
    #define ecalloc(x,y) calloc((x),(y))
    #define erealloc(x,y) realloc((x),(y))
    #define emalloc(x) malloc(x)
    #define efree(x) free(x)
#endif

/** Default number of elements in a MemBlock */
#ifndef GROW_BY
#define GROW_BY 25
#endif

/** @name Helper Functions
* @{
*/

/** Allocates a new MemBlock 
* @param[in] info The MemBlockInfo struct to use
* @return A pointer to the new MemBlock
*/
struct MemBlock* memNewBlock(struct MemBlockInfo* info);

/** Resizes the @c strings buffer in a MemBlockInfo struct.
* @param[in] info The MemBlockInfo struct to use.
* @return @c 0 on Success, @c 1 on Failure
*/
int memStringsGrow(struct MemBlockInfo* info);

/** Iterates over all allocated strings and calls @c free() on each one, then
* frees the @c strings buffer.
* @param[in] info The MemBlockInfo struct to use.
*/
void memFreeStrings(struct MemBlockInfo* info);

/**@}*/

/** Initializes a new chain of MemBlocks.
* @note memInit() calls memInitEx() with a default number of elements defined
*       by GROW_BY. If you'd like a different number of
*       elements per MemBlock, use memInitEx() instead.
* @note memInit() needs to be called only once per group of elements.
*       A call to memFree() will free all the elements in the MemBlock chain
*       described by MemBlockInfo. 
* @note The MemBlock struct is zero-filled, which means all elements returned
*       by memGet() will be already initialized with zeros.
* @param[in] size The size of an element to be allocated by memGet()
* @return On Success, a MemBlockInfo struct used to manage the new MemBlocks or
*         @c NULL on Failure.
* @see memInit()
* @see memGet()
*/
struct MemBlockInfo* memInit(size_t size)
{
    return memInitEx(size,GROW_BY); /* Call memInitEx() with default GROW_BY */
}

/** Initializes a new chain of MemBlocks with a custom number of elements per
* block.
* @note memInit() needs to be called only once per group of elements.
*       A call to memFree() will free all the elements in the MemBlock chain
*       described by MemBlockInfo. 
* @note The MemBlock struct is zero-filled, which means all elements returned
*       by memGet() will be already initialized with zeros.
* @param[in] size    The size of an element to be allocated by memGet()
* @param[in] grow_by The number of elements in each MemBlock
* @return On Success, a MemBlockInfo struct used to manage the new MemBlocks or
*         @c NULL on Failure.
* @see memInit()
* @see memGet()
*/
struct MemBlockInfo* memInitEx(size_t size, unsigned int grow_by)
{
    struct MemBlockInfo* result=NULL;               /* the result */

    assert(size);                                   /* size must not be 0 */
    assert(grow_by);                                /* grow_by must not be 0 */

    /* Allocate space for the info block */
    result=ecalloc(1,sizeof(*result));              /* alloc and zero-fill */

    if (!result)                                    /* check for errors */
        return NULL;

    /* Allocate a special block for strings */
    result->strings=ecalloc(grow_by,sizeof(char*)); /* alloc and zero-fill */

    if (!result->strings)                           /* check for errors */
    {
        efree(result);                              /* prevent leaks */
        return NULL;
    }

    result->elemSize=size;                          /* default element size */
    result->elemPerBlock=grow_by;                   /* # of elems per block */

    return result;                                  /* return the new block */
}

/** Reserves a new element. This function does @b not use @c malloc() on
* every call. Instead, it uses a MemBlock to compute the address of the next
* available element. If the MemBlock is full, it will allocate a new MemBlock.
* @note The size of the elements allocated by memGet() is always the same.
*       It must be specified as the first parameter of memInit() or memInitEx()
* @warning Just like @c malloc(), memGet() will return @c NULL if it cannot
*          allocate more memory.
* @return A pointer to a new element on Success, @c NULL on Failure
* @see memInit()
* @see memInitEx()
*/
void* memGet(struct MemBlockInfo* info)
{
    void* result=NULL;                          /* pointer to the new elem */

    assert(info);                               /* need a valid MemBlockInfo */

    /* Create a new block if none exists, or the last one is full */
    if (!info->last || (info->last->allocated==info->elemPerBlock))
        memNewBlock(info);

    if (!info->last)                            /* memNewBlock could fail */
        return NULL;

    /* Compute the address of the new element. It's the address of the data
      buffer, plus the number of already allocated elements times their size */
    result=((unsigned char*)info->last->data) +
           info->last->allocated * info->elemSize;

    ++info->last->allocated; /* Increment number of elements in the block */
    ++info->elemTotal;       /* Increment number total number of elements */

    return result;
}

/** Frees all memory associated with a MemBlockInfo. This includes the list of
* MemBlocks and the @c strings buffer.
* @param[in] info The MemBlockInfo to free
* @warning calls @c free()
*/
void memFree(struct MemBlockInfo* info)
{
    struct MemBlock* block=NULL;    /* block to free */
    struct MemBlock* next=NULL;     /* next block to free */

    assert(info);                   /* ensure MemBlockInfo is valid */

    block=info->first;              /* start with the first MemBlock */

    while(block)                    /* loop over all blocks */
    {
        next=block->next;           /* save the pointer to the next block */
        efree(block->data);         /* free the data buffer */
        efree(block);               /* free the MemBlock */
        block=next;                 /* move to the next block */
    }

    memFreeStrings(info);           /* free all strings */

    efree(info);                    /* Finally, free the MemBlockInfo */
}

/** Duplicates a string into the MemBlockInfo string buffer.The string will be 
* free'd together with the MemBlocks when memFree() is called.
* @note If the length of the string is already known, use memNewStringEx()
* @param[in] s     A NULL-terminated string
* @param[in] info  The MemBockInfo struct which will hold the new string data.
* @return A pointer to the new data on Success or @c NULL on Failure
* @note memNewString() is similar in intent to @c strdup()
* @see memString()
* @see memStringEx()
*/
char* memNewString(const char* s, struct MemBlockInfo* info)
{
    char* result=NULL;              /* the result */
    size_t len=0;                   /* length of s */

    assert(s);                      /* need a valid string */
    assert(info);                   /* need a valid MemBlockInfo */

    len=strlen(s);                  /* count the length of s */

    result=memStringEx(len+1,info); /* allocate space for the new string */
    if (!result)                    /* check for errors */
        return NULL;

    memcpy(result,s,len);           /* Copy the data */
    result[len]='\0';               /* Ensure the string is NULL-terminated */

    return result;                  /* return the result */
}

/** Duplicates a string into the MemBlockInfo string buffer.The string will be 
* free'd together with the MemBlocks when memFree() is called. 
* @note If the length of the string is @c not known, use memNewString() will
*       call strlen() for you.
* @warning The value of @c length should be the same as if @c strlen(s) were
*          called, i.e. the NULL character should not be included.
* @param[in] s      A NULL-terminated string
* @param[in] len    The length of the string, @b not including the NULL char
* @param[in] info   The MemBockInfo struct which will hold the new string data.
* @return A pointer to the new data on Success or @c NULL on Failure
* @note memNewStringEx() is similar in intent to @c strndup()
* @see memString()
* @see memStringEx()
*/
char* memNewStringEx(const char* s, size_t len, struct MemBlockInfo* info)
{
    char* result=NULL;              /* the result */

    assert(s);                      /* need a valid string */
    assert(info);                   /* need a valid MemBlockInfo */

    result=memStringEx(len+1,info); /* allocate space for the new string */
    if (!result)                    /* check for errors */
        return NULL;

    memcpy(result,s,len);           /* Copy the data */
    result[len]='\0';               /* Ensure the string is NULL-terminated */

    return result;                  /* return the result */
}


/** Allocates memory for a new string. Grows the string container if necessary.
* @note uses @c malloc()
* @param[in] length  Number of chars, @c including the NULL character
* @param[in] info    The MemBlockInfo struct that will hold the new string data
* @return A pointer to the new data (@b not zero-filled)
* @see memStringEx()
*/
char* memString(size_t length, struct MemBlockInfo* info)
{
    char* result=NULL;              /* the result */

    assert(info);                   /* need a valid MemBlockInfo */
    assert(info->strings);          /* need a valid strings buffer */

    /* Grow the strings container, if necessary */
    if (info->stringCount==info->stringsMax)
    {
        if (!memStringsGrow(info))  /* check for errors */
            return NULL;
    }

    result=emalloc(length*sizeof(char));    /* allocate the space */

    /* Store the pointer to the newly allocated string */
    info->strings[info->stringCount]=result;
    ++info->stringCount;

    return result;
}

/** Allocates memory for a new string. Grows the string container if necessary.
* @note uses @c calloc()
* @param[in] length  Number of chars, @c including the NULL character
* @param[in] info    The MemBlockInfo struct that will hold the new string data
* @return A pointer to the new data (zero-filled)
* @see memString()
*/
char* memStringEx(size_t length, struct MemBlockInfo* info)
{
    char* result=NULL;

    assert(info);
    assert(info->strings);

    /* Grow the strings container, if necessary */
    if (info->stringCount==info->stringsMax)
    {
        if (!memStringsGrow(info))
            return NULL;
    }

    /* Using calloc to help with null-terminated strings ;) */
        result=ecalloc(length,sizeof(char));

    /* Store the pointer to the newly allocated string */
    info->strings[info->stringCount]=result;
    ++info->stringCount;

    return result;
}

/** Allocates a new MemBlock 
* @param[in] info The MemBlockInfo struct to use
* @return A pointer to the new MemBlock
*/
struct MemBlock* memNewBlock(struct MemBlockInfo* info)
{
    struct MemBlock* result=NULL;

    assert(info);
    assert(info->elemSize);

    result=ecalloc(1,sizeof(*result));

    if (!result)
        return NULL;

    /* Make sure data blocks are zeroed out */
    result->data=ecalloc(info->elemPerBlock,info->elemSize);

    /* Memory alloc error? */
    if (!result->data)
    {
        efree(result);
        return NULL;
    }

    result->info=info;
    result->allocated=0;

    /* If this is the first MemBlock in the series */
    if (!info->last)
    {
        result->previous = result->next = NULL;
        info->first = info->last = result;
    }
    else
    /* Append new MemBlock */
    {
        result->next=NULL;
        result->previous=info->last;
        result->previous->next=result;

        info->last=result;
    }

    ++info->blockCount;

    return result;
}

/** Resizes the @c strings buffer in a MemBlockInfo struct.
* @param[in] info The MemBlockInfo struct to use.
* @return @c 0 on Success, @c 1 on Failure
*/
int memStringsGrow(struct MemBlockInfo* info)
{
    void* result=NULL;

    assert(info);
    assert(info->strings);
    assert(info->elemPerBlock);

    info->stringsMax+=info->elemPerBlock;

    /* Using 'result' here (instead of info->strings), to allow 
    memblockFree() to free info->strings if realloc fails */
    result=erealloc(info->strings,sizeof(char*) * info->stringsMax);

    if (!result)
        return 0;

    info->strings=result;   /* ok, store the new pointer */

    /* Make sure the newly allocated space is zeroed out */
    memset(info->strings+info->stringCount,0,sizeof(char*) * 
           (info->stringsMax - info->stringCount));

    return 1;
}

/** Iterates over all allocated strings and calls @c free() on each one, then
* frees the @c strings buffer.
* @param[in] info The MemBlockInfo struct to use.
*/
void memFreeStrings(struct MemBlockInfo* info)
{
    unsigned int i;

    assert(info);
    assert(info->strings);

    /* Free each string */
    for (i=0;i<info->stringCount;++i)
        efree(info->strings[i]);

    /* Free the string container */
    efree(info->strings);
    info->strings=NULL; /* kontrol'nyi */
}
